#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @FileName  :chapter04.py
# @Time      :2021/03/26 15:34:08
# @Author    :Raink

from typing import List, Optional, Union
from fastapi import APIRouter, status
from pydantic import BaseModel, EmailStr

app04 = APIRouter()

"""响应模型 Response Model"""


class UserBase(BaseModel):
    username: str
    email: EmailStr
    mobile: str = "10001"
    full_name: Optional[str] = None


class UserIn(UserBase):
    password: str


class UserOut(UserBase):
    pass


users = {
    "user01": {"username":"user01", "passworld":"123456", "email":"user01@example.com", "full_name": "user-user01"},
    "user02": {"username":"user02", "passworld":"abcdef", "email":"user02@example.com", "full_name": "user-user02"}
}

# path optional路径操作
# response_model_exclude_unset=True,
#    模型中默认值不被包含到响应中,仅包含实际给的值
@app04.post("/response_model", response_model=UserOut)
async def response_model(user: UserIn):
    print(user.password)
    return users["user01"]
    # users["user01"]数据中有password值，但是按照UserOut返回

# Union,并集，
# 还可以使用List[UserOut]来定义响应模型
@app04.post(
    "/response_model/attributes",
    response_model=Union[UserIn, UserOut]
)
async def response_model_attributes(user: UserIn):
    # del user.password，并集之后会返回password，可以使用del来删除某些属性
    return user

    
"""
响应状态码
"""

@app04.post("/status_code", status_code=status.HTTP_200_OK)
async def status_code():
    print(type(status.HTTP_200_OK), status.HTTP_200_OK)
    return {"status_code": status.HTTP_200_OK}

"""
表单数据处理
Form Data
"""
# Form是来自于python-multipart库的，必须先pip安装 
from fastapi import Form

@app04.post("/login")
async def login(username: str=Form(...), password:str=Form(...)):
    return {"username": username}

"""
文件上传及参数详情
"""
from fastapi import File, UploadFile

# 单个文件，小文件！
# File类，使用的字节形式写入内存，只适用于小文件，大文件会占满内存。
@app04.post("/file")
async def file_(file: bytes=File(...)):
    return {"file_size": len(file)}

# 多个文件，较大文件，使用UploadFile
# 先使用内存，当达到一定的阀值，会保存到磁盘中
# 适合于图片、视频等大文件
# 可以获取文件的元数据，如文件名、创建时间等
# 有文件对象的异步接口
# 上传的文件中Python文件对象，可以使用write(),read(),seek(), closs()操作
@app04.post("/upload_file")
async def upload_file(file: UploadFile = File(...)):
    return {"file_name": file.filename}

@app04.post("/upload_files")
async def upload_file(files: List[UploadFile] = File(...)):
    for file in files:
        # await ,因为使用了async,读操作使用await
        contents = await file.read()
        print(contents)
    return {
        "file_name": [file.filename for file in files],
        "content_type": [file.content_type for file in files]
    }

"""
静态文件的配置
在主文件内 【run.py】
注意不能挂载到APIRouter()实例中，只能挂载到FastAPI()实例中
"""

"""
---------------------------------------------------------------------
"""

"""
路径操作配置
"""
@app04.post(
    "/path_operation_configuration",
    response_model=UserOut,
    tags=["Path","Operation","Configuration"], # 和主程序中的tags是一样的效果
    summary="This is summary",
    description="This is description",
    response_description="This is response description",
    # deprecated=True,  # 表示接口已废弃，但可继续使用
    status_code=status.HTTP_200_OK
)
async def path_operation_configuration(user: UserIn):
    return user.dict()

"""
FastAPI应用常见配置
在主文件内 【run.py】
app = FastAPI(
    title="FastAPI Tutorial and Coronavirus Tracker API Docs",
    description='FastAPI教程 新冠病毒疫情跟踪API接口文档',
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redocs"
)
"""

"""
---------------------------------------------------------------------
"""

"""
错误处理
"""
from fastapi import HTTPException

@app04.get("/http_exception")
async def http_exception(city: str):
    if city != "Beijing":
        raise HTTPException(
            status_code=404, 
            detail="City Not found",
            headers={"X-Err":"Error"}
        )
    return {"city": city}

"""
若要重写HTTPException错误处理方法
需要写在主应用程序中【run.py】
# from fastapi.exceptions import HTTPException
from starlette.exceptions import HTTPException as StarletteHTTPException

@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    # request参数不可省
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)
"""
@app04.get("/http_exception/{city_id}")
async def override_http_exception(city_id: int):
    if city_id == 1:
        raise HTTPException(
            status_code=418, 
            detail="Nope! Idon`t like 1."
        )
    # 因为run.py中已经定义的 @app.exception_handler，
    # 是以字符串的形式返回，所以这里不会返回字典，而是字符串（文本）    
    return {"city_id": city_id}

"""
对于run.py中已经重写的
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)

使用chapter03/query/bool方法进行验证,
其可以将 false、true、on、1、0、等进行转换，这里传入“a”
http://192.168.1.52:4539/chapter03/query/bool/conversion?param=a
原本会以json字典的形式返回
如：{"detail":[{"loc":["query","param"],"msg":"value could not be parsed to a boolean","type":"type_error.bool"}]}
而现在会直接以文本形式返回：
1 validation error for Request
query -> param
  value could not be parsed to a boolean (type=type_error.bool)
"""